package com.ebspp.server.storage;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import com.ebspp.global.General;
import com.ebspp.thrift.DataMsg;
import com.ebspp.thrift.Identity;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;
import com.sleepycat.persist.StoreConfig;

/**
 * BDBStorage, supported by the underlying Berkeley DB java edition.
 * In this class, a sync is called after each bulkWrite.
 * Hence, a return from the call means that the data are totally persistent.
 * 
 * Note that the server may fail during the write and partial write doesn't exist.
 * Use the TBDBStore for ACID transaction support
 * 
 * Note the key to byte transformation should always use an explicit encoding scheme.
 * The default encoding depends on the java and db version.
 * @author x23pan
 *
 */
public class BDBStore implements IStore
{
	Environment env = null;
	Database db = null;

	Identity id;
	String dbDir;
	DataAccess da;
	
	public BDBStore(Identity id) throws ResourceInitException
	{
		this.id = id;
		dbDir = General.dir + "/store/BDBStore/" + id;
		File dbDirFile = new File(dbDir);
		if(!dbDirFile.exists())
		{
			if(dbDirFile.mkdirs())
				System.out.println("Creating bdb store dir at: " + dbDir);
			else
				throw new ResourceInitException("Unable to create the db dir at:" + dbDir);
		}
		
		EnvironmentConfig envConf = new EnvironmentConfig();
		envConf.setAllowCreate(true);
		env = new Environment(new File(dbDir), envConf);
		
		DatabaseConfig dbConf = new DatabaseConfig();
		dbConf.setAllowCreate(true);
		db = env.openDatabase(null, "bdbStore", dbConf);
		
		da = new DataAccess(db);
	}
	
	
	public boolean write(DataMsg msg) {
		// TODO Auto-generated method stub
		return da.put(msg);
	}

	public DataMsg read(DataMsg msg) 
	{
		return da.get(msg);
	}
	
	public boolean delete(DataMsg msg)
	{
		return da.delete(msg);
	}

	@Override
	public List<DataMsg> bulkWrite(List<DataMsg> data) 
	{
		// TODO Auto-generated method stub
		if(data == null)
			return null;

		List<DataMsg> failList = new ArrayList<DataMsg>(0);
		for(DataMsg msg : data)
		{
			if(!write(msg))
				failList.add(msg);
		}
		return failList;
	}

	@Override
	public List<DataMsg> bulkRead(List<DataMsg> data) 
	{
		// TODO Auto-generated method stub
		if(data == null)
			return null;
		
		ArrayList<DataMsg> finList = new ArrayList<DataMsg>();
		for(DataMsg msg : data)
			finList.add(read(msg));
		
		return finList;
	}
	
	@Override
	public List<DataMsg> garbageCollect(List<DataMsg> data) 
	{
		// TODO Auto-generated method stub
		List<DataMsg> failList = new ArrayList<DataMsg>(0);
		
		if(data == null)
			return null;
		
		for(DataMsg msg : data)
			if(!delete(msg))
				failList.add(msg);

		return failList;
	}
	

	@Override
	public boolean close() throws DatabaseException
	{
		// TODO Auto-generated method stub
		db.close();
		env.close();

		return true;
	}

	
	class DataAccess
	{
		Database db;
		public DataAccess(Database db)
		{
			this.db = db;
		}
		
		public boolean put(DataMsg msg)
		{
			try
			{
				db.put(null, new DatabaseEntry(msg.getKey().getBytes()), new DatabaseEntry(msg.getValue()));
			}catch(DatabaseException dbe)
			{
				System.err.println("Fail to read:" + msg.getKey());
				return false;
			}
			return true;
		}
		
		public DataMsg get(DataMsg msg)
		{
			DatabaseEntry value = new DatabaseEntry();
			try
			{
				db.get(null, new DatabaseEntry(msg.getKey().getBytes()), value	, LockMode.DEFAULT);
				msg.setValue(value.getData());
			}catch(DatabaseException dbe)
			{
				System.err.println("Fail to write:" + msg.getKey());
				return null;
			}
			return msg;
		}
		public boolean delete(DataMsg msg)
		{
			try
			{
				db.delete(null, new DatabaseEntry(msg.getKey().getBytes()));
			}catch(DatabaseException dbe)
			{
				System.err.println("Fail to delete:" + msg.getKey());
				return false;
			}
			return true;
		}
	}
	
	
	public static void main(String[] args) throws ResourceInitException
	{
		BDBStore bdb = new BDBStore(new Identity());
		
		List<DataMsg> msgList = new ArrayList<DataMsg>();
		
		DataMsg msg2 = new DataMsg();
		msg2.setKey("1");
		msg2.setValue("2".getBytes());
		msg2.setTs("1");
		msgList.add(msg2);
		
		DataMsg msg1 = new DataMsg();
		msg1.setKey("1");
		msg1.setValue("1".getBytes());
		msg1.setTs("2");
		msgList.add(msg1);
		
		
		bdb.bulkWrite(msgList);
		
		bdb.bulkRead(msgList);
	}
}
